This repository has been archived by the owner on Nov 3, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
AudioConverterFileConvert.cpp
705 lines (566 loc) · 33.2 KB
/
AudioConverterFileConvert.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
/*
File: AudioConverterFileConvert.cpp
Abstract: Demonstrates converting audio using AudioConverterFillComplexBuffer.
Version: 1.0.3
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple
Inc. ("Apple") in consideration of your agreement to the following
terms, and your use, installation, modification or redistribution of
this Apple software constitutes acceptance of these terms. If you do
not agree with these terms, please do not use, install, modify or
redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a personal, non-exclusive
license, under Apple's copyrights in this original Apple software (the
"Apple Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple. Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2014 Apple Inc. All Rights Reserved.
*/
// standard includes
#include <AudioToolbox/AudioToolbox.h>
// helpers
#include "CAXException.h"
#include "CAStreamBasicDescription.h"
#include <pthread.h>
/*
For more information on the importance of interruption handling and Audio Session setup when performing offline
encoding please see the Audio Session Programming Guide.
Offline format conversion requires interruption handling. Specifically, you must handle interruptions at the audio data buffer level.
By way of background, you can use a hardware assisted-codec—on certain devices—to encode linear PCM audio to AAC format.
The codec is available on the iPhone 3GS and on the iPod touch (2nd generation), but not on older models. You use the codec as part
of an Audio Converter object (of type AudioConverterRef).
For information on these opaque types, refer to Audio Converter Services Reference and Extended Audio File Services Reference.
To handle an interruption during hardware-assisted encoding, take two things into account:
1. The codec may or may not be able to resume encoding after the interruption ends.
2. The codec may be unavailable, probably due to an interruption.
Note: iOS 7 provides for software AAC encode, devices with hardware encoder will show as having two encoders, devices such as the iPhone 5s only has
a software encoder that is much faster and more flexible than the older hardware encoders.
Encoding takes place as you repeatedly call the AudioConverterFillComplexBuffer function supplying new buffers of input audio data via the input data procedure
producing buffers of audio encoded in the output format.
To handle an interruption, you respond to the function’s result code, as described here:
• kAudioConverterErr_HardwareInUse — This result code indicates that the underlying hardware codec has become unavailable, probably due to an interruption.
In this case, your application must stop calling AudioConverterFillComplexBuffer. If you can resume conversion, wait for an interruption-ended call from
the audio session. In your interruption-end handler, reactivate the session and then resume converting the audio data.
To check if the AAC codec can resume, obtain the value of the associated converter’s kAudioConverterPropertyCanResumeFromInterruption property.
The value is 1 (can resume) or 0 (cannot resume) or the property itself may not be supported (implies software codec use where we can resume).
You can obtain this value any time after instantiating the converter—immediately after instantiation, upon interruption, or after interruption ends.
If the converter cannot resume, then on interruption you must abandon the conversion. After the interruption ends, or after the user relaunches your application
and indicates they want to resume conversion, re-instantiate the extended audio file object and perform the conversion again.
*/
#pragma mark- Thread State
/* Since we perform conversion in a background thread, we must ensure that we handle interruptions appropriately.
In this sample we're using a mutex protected variable tracking thread states. The background conversion threads state transistions from Done to Running
to Done unless we've been interrupted in which case we are Paused blocking the conversion thread and preventing further calls
to AudioConverterFillComplexBuffer (since it would fail if we were using the hardware codec).
Once the interruption has ended, we unblock the background thread as the state transitions to Running once again.
Any errors returned from AudioConverterFillComplexBuffer must be handled appropriately. Additionally, if the Audio Converter cannot
resume conversion after an interruption, you should not call AudioConverterFillComplexBuffer again.
*/
static pthread_mutex_t sStateLock; // protects sState
static pthread_cond_t sStateChanged; // signals when interruption thread unblocks conversion thread
enum ThreadStates {
kStateRunning,
kStatePaused,
kStateDone
};
static ThreadStates sState;
// initialize the thread state
void ThreadStateInitalize()
{
int rc;
assert([NSThread isMainThread]);
rc = pthread_mutex_init(&sStateLock, NULL);
assert(rc == 0);
rc = pthread_cond_init(&sStateChanged, NULL);
assert(rc == 0);
sState = kStateDone;
}
// handle begin interruption - transition to kStatePaused
void ThreadStateBeginInterruption()
{
int rc;
assert([NSThread isMainThread]);
rc = pthread_mutex_lock(&sStateLock);
assert(rc == 0);
if (sState == kStateRunning) {
sState = kStatePaused;
}
rc = pthread_mutex_unlock(&sStateLock);
assert(rc == 0);
}
// handle end interruption - transition to kStateRunning
void ThreadStateEndInterruption()
{
int rc;
assert([NSThread isMainThread]);
rc = pthread_mutex_lock(&sStateLock);
assert(rc == 0);
if (sState == kStatePaused) {
sState = kStateRunning;
rc = pthread_cond_signal(&sStateChanged);
assert(rc == 0);
}
rc = pthread_mutex_unlock(&sStateLock);
assert(rc == 0);
}
// set state to kStateRunning
void ThreadStateSetRunning()
{
int rc = pthread_mutex_lock(&sStateLock);
assert(rc == 0);
assert(sState == kStateDone);
sState = kStateRunning;
rc = pthread_mutex_unlock(&sStateLock);
assert(rc == 0);
}
// block for state change to kStateRunning
Boolean ThreadStatePausedCheck()
{
Boolean wasInterrupted = false;
int rc = pthread_mutex_lock(&sStateLock);
assert(rc == 0);
assert(sState != kStateDone);
while (sState == kStatePaused) {
rc = pthread_cond_wait(&sStateChanged, &sStateLock);
assert(rc == 0);
wasInterrupted = true;
}
// we must be running or something bad has happened
assert(sState == kStateRunning);
rc = pthread_mutex_unlock(&sStateLock);
assert(rc == 0);
return wasInterrupted;
}
void ThreadStateSetDone()
{
int rc = pthread_mutex_lock(&sStateLock);
assert(rc == 0);
assert(sState != kStateDone);
sState = kStateDone;
rc = pthread_mutex_unlock(&sStateLock);
assert(rc == 0);
}
// ***********************
#pragma mark- Converter
/* The main Audio Conversion function using AudioConverter */
enum {
kMyAudioConverterErr_CannotResumeFromInterruptionError = 'CANT',
eofErr = -39 // End of file
};
typedef struct {
AudioFileID srcFileID;
SInt64 srcFilePos;
char * srcBuffer;
UInt32 srcBufferSize;
CAStreamBasicDescription srcFormat;
UInt32 srcSizePerPacket;
UInt32 numPacketsPerRead;
AudioStreamPacketDescription *packetDescriptions;
} AudioFileIO, *AudioFileIOPtr;
#pragma mark-
// Input data proc callback
static OSStatus EncoderDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData)
{
AudioFileIOPtr afio = (AudioFileIOPtr)inUserData;
OSStatus error;
// figure out how much to read
UInt32 maxPackets = afio->srcBufferSize / afio->srcSizePerPacket;
if (*ioNumberDataPackets > maxPackets) *ioNumberDataPackets = maxPackets;
// read from the file
UInt32 outNumBytes;
error = AudioFileReadPackets(afio->srcFileID, false, &outNumBytes, afio->packetDescriptions, afio->srcFilePos, ioNumberDataPackets, afio->srcBuffer);
if (eofErr == error) error = noErr;
if (error) { printf ("Input Proc Read error: %ld (%4.4s)\n", error, (char*)&error); return error; }
//printf("Input Proc: Read %lu packets, at position %lld size %lu\n", *ioNumberDataPackets, afio->srcFilePos, outNumBytes);
// advance input file packet position
afio->srcFilePos += *ioNumberDataPackets;
// put the data pointer into the buffer list
ioData->mBuffers[0].mData = afio->srcBuffer;
ioData->mBuffers[0].mDataByteSize = outNumBytes;
ioData->mBuffers[0].mNumberChannels = afio->srcFormat.mChannelsPerFrame;
// don't forget the packet descriptions if required
if (outDataPacketDescription) {
if (afio->packetDescriptions) {
*outDataPacketDescription = afio->packetDescriptions;
} else {
*outDataPacketDescription = NULL;
}
}
return error;
}
#pragma mark-
// Some audio formats have a magic cookie associated with them which is required to decompress audio data
// When converting audio data you must check to see if the format of the data has a magic cookie
// If the audio data format has a magic cookie associated with it, you must add this information to anAudio Converter
// using AudioConverterSetProperty and kAudioConverterDecompressionMagicCookie to appropriately decompress the data
// http://developer.apple.com/mac/library/qa/qa2001/qa1318.html
static void ReadCookie(AudioFileID sourceFileID, AudioConverterRef converter)
{
// grab the cookie from the source file and set it on the converter
UInt32 cookieSize = 0;
OSStatus error = AudioFileGetPropertyInfo(sourceFileID, kAudioFilePropertyMagicCookieData, &cookieSize, NULL);
// if there is an error here, then the format doesn't have a cookie - this is perfectly fine as some formats do not
if (noErr == error && 0 != cookieSize) {
char* cookie = new char [cookieSize];
error = AudioFileGetProperty(sourceFileID, kAudioFilePropertyMagicCookieData, &cookieSize, cookie);
if (noErr == error) {
error = AudioConverterSetProperty(converter, kAudioConverterDecompressionMagicCookie, cookieSize, cookie);
if (error) printf("Could not Set kAudioConverterDecompressionMagicCookie on the Audio Converter!\n");
} else {
printf("Could not Get kAudioFilePropertyMagicCookieData from source file!\n");
}
delete [] cookie;
}
}
// Some audio formats have a magic cookie associated with them which is required to decompress audio data
// When converting audio, a magic cookie may be returned by the Audio Converter so that it may be stored along with
// the output data -- This is done so that it may then be passed back to the Audio Converter at a later time as required
static void WriteCookie(AudioConverterRef converter, AudioFileID destinationFileID)
{
// grab the cookie from the converter and write it to the destinateion file
UInt32 cookieSize = 0;
OSStatus error = AudioConverterGetPropertyInfo(converter, kAudioConverterCompressionMagicCookie, &cookieSize, NULL);
// if there is an error here, then the format doesn't have a cookie - this is perfectly fine as some formats do not
if (noErr == error && 0 != cookieSize) {
char* cookie = new char [cookieSize];
error = AudioConverterGetProperty(converter, kAudioConverterCompressionMagicCookie, &cookieSize, cookie);
if (noErr == error) {
error = AudioFileSetProperty(destinationFileID, kAudioFilePropertyMagicCookieData, cookieSize, cookie);
if (noErr == error) {
printf("Writing magic cookie to destination file: %ld\n", cookieSize);
} else {
printf("Even though some formats have cookies, some files don't take them and that's OK\n");
}
} else {
printf("Could not Get kAudioConverterCompressionMagicCookie from Audio Converter!\n");
}
delete [] cookie;
}
}
// Write output channel layout to destination file
static void WriteDestinationChannelLayout(AudioConverterRef converter, AudioFileID sourceFileID, AudioFileID destinationFileID)
{
UInt32 layoutSize = 0;
bool layoutFromConverter = true;
OSStatus error = AudioConverterGetPropertyInfo(converter, kAudioConverterOutputChannelLayout, &layoutSize, NULL);
// if the Audio Converter doesn't have a layout see if the input file does
if (error || 0 == layoutSize) {
error = AudioFileGetPropertyInfo(sourceFileID, kAudioFilePropertyChannelLayout, &layoutSize, NULL);
layoutFromConverter = false;
}
if (noErr == error && 0 != layoutSize) {
char* layout = new char[layoutSize];
if (layoutFromConverter) {
error = AudioConverterGetProperty(converter, kAudioConverterOutputChannelLayout, &layoutSize, layout);
if (error) printf("Could not Get kAudioConverterOutputChannelLayout from Audio Converter!\n");
} else {
error = AudioFileGetProperty(sourceFileID, kAudioFilePropertyChannelLayout, &layoutSize, layout);
if (error) printf("Could not Get kAudioFilePropertyChannelLayout from source file!\n");
}
if (noErr == error) {
error = AudioFileSetProperty(destinationFileID, kAudioFilePropertyChannelLayout, layoutSize, layout);
if (noErr == error) {
printf("Writing channel layout to destination file: %ld\n", layoutSize);
} else {
printf("Even though some formats have layouts, some files don't take them and that's OK\n");
}
}
delete [] layout;
}
}
// Sets the packet table containing information about the number of valid frames in a file and where they begin and end
// for the file types that support this information.
// Calling this function makes sure we write out the priming and remainder details to the destination file
static void WritePacketTableInfo(AudioConverterRef converter, AudioFileID destinationFileID)
{
UInt32 isWritable;
UInt32 dataSize;
OSStatus error = AudioFileGetPropertyInfo(destinationFileID, kAudioFilePropertyPacketTableInfo, &dataSize, &isWritable);
if (noErr == error && isWritable) {
AudioConverterPrimeInfo primeInfo;
dataSize = sizeof(primeInfo);
// retrieve the leadingFrames and trailingFrames information from the converter,
error = AudioConverterGetProperty(converter, kAudioConverterPrimeInfo, &dataSize, &primeInfo);
if (noErr == error) {
// we have some priming information to write out to the destination file
/* The total number of packets in the file times the frames per packet (or counting each packet's
frames individually for a variable frames per packet format) minus mPrimingFrames, minus
mRemainderFrames, should equal mNumberValidFrames.
*/
AudioFilePacketTableInfo pti;
dataSize = sizeof(pti);
error = AudioFileGetProperty(destinationFileID, kAudioFilePropertyPacketTableInfo, &dataSize, &pti);
if (noErr == error) {
// there's priming to write out to the file
UInt64 totalFrames = pti.mNumberValidFrames + pti.mPrimingFrames + pti.mRemainderFrames; // get the total number of frames from the output file
printf("Total number of frames from output file: %lld\n", totalFrames);
pti.mPrimingFrames = primeInfo.leadingFrames;
pti.mRemainderFrames = primeInfo.trailingFrames;
pti.mNumberValidFrames = totalFrames - pti.mPrimingFrames - pti.mRemainderFrames;
error = AudioFileSetProperty(destinationFileID, kAudioFilePropertyPacketTableInfo, sizeof(pti), &pti);
if (noErr == error) {
printf("Writing packet table information to destination file: %ld\n", sizeof(pti));
printf(" Total valid frames: %lld\n", pti.mNumberValidFrames);
printf(" Priming frames: %ld\n", pti.mPrimingFrames);
printf(" Remainder frames: %ld\n", pti.mRemainderFrames);
} else {
printf("Some audio files can't contain packet table information and that's OK\n");
}
} else {
printf("Getting kAudioFilePropertyPacketTableInfo error: %ld\n", error);
}
} else {
printf("No kAudioConverterPrimeInfo available and that's OK\n");
}
} else {
printf("GetPropertyInfo for kAudioFilePropertyPacketTableInfo error: %ld, isWritable: %ld\n", error, isWritable);
}
}
#pragma mark-
OSStatus DoConvertFile(CFURLRef sourceURL, CFURLRef destinationURL, OSType outputFormat, Float64 outputSampleRate)
{
AudioFileID sourceFileID = 0;
AudioFileID destinationFileID = 0;
AudioConverterRef converter = NULL;
Boolean canResumeFromInterruption = true; // we can continue unless told otherwise
CAStreamBasicDescription srcFormat, dstFormat;
AudioFileIO afio = {};
char *outputBuffer = NULL;
AudioStreamPacketDescription *outputPacketDescriptions = NULL;
OSStatus error = noErr;
// in this sample we should never be on the main thread here
assert(![NSThread isMainThread]);
// transition thread state to kStateRunning before continuing
ThreadStateSetRunning();
printf("\nDoConvertFile\n");
try {
// get the source file
XThrowIfError(AudioFileOpenURL(sourceURL, kAudioFileReadPermission, 0, &sourceFileID), "AudioFileOpenURL failed");
// get the source data format
UInt32 size = sizeof(srcFormat);
XThrowIfError(AudioFileGetProperty(sourceFileID, kAudioFilePropertyDataFormat, &size, &srcFormat), "couldn't get source data format");
// setup the output file format
dstFormat.mSampleRate = (outputSampleRate == 0 ? srcFormat.mSampleRate : outputSampleRate); // set sample rate
if (outputFormat == kAudioFormatLinearPCM) {
// if the output format is PC create a 16-bit int PCM file format description as an example
dstFormat.mFormatID = outputFormat;
dstFormat.mChannelsPerFrame = srcFormat.NumberChannels();
dstFormat.mBitsPerChannel = 16;
dstFormat.mBytesPerPacket = dstFormat.mBytesPerFrame = 2 * dstFormat.mChannelsPerFrame;
dstFormat.mFramesPerPacket = 1;
dstFormat.mFormatFlags = kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger; // little-endian
} else {
// compressed format - need to set at least format, sample rate and channel fields for kAudioFormatProperty_FormatInfo
dstFormat.mFormatID = outputFormat;
dstFormat.mChannelsPerFrame = (outputFormat == kAudioFormatiLBC ? 1 : srcFormat.NumberChannels()); // for iLBC num channels must be 1
// use AudioFormat API to fill out the rest of the description
size = sizeof(dstFormat);
XThrowIfError(AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &size, &dstFormat), "couldn't create destination data format");
}
printf("Source File format: "); srcFormat.Print();
printf("Destination format: "); dstFormat.Print();
// create the AudioConverter
XThrowIfError(AudioConverterNew(&srcFormat, &dstFormat, &converter), "AudioConverterNew failed!");
// if the source has a cookie, get it and set it on the Audio Converter
ReadCookie(sourceFileID, converter);
// get the actual formats back from the Audio Converter
size = sizeof(srcFormat);
XThrowIfError(AudioConverterGetProperty(converter, kAudioConverterCurrentInputStreamDescription, &size, &srcFormat), "AudioConverterGetProperty kAudioConverterCurrentInputStreamDescription failed!");
size = sizeof(dstFormat);
XThrowIfError(AudioConverterGetProperty(converter, kAudioConverterCurrentOutputStreamDescription, &size, &dstFormat), "AudioConverterGetProperty kAudioConverterCurrentOutputStreamDescription failed!");
printf("Formats returned from AudioConverter:\n");
printf(" Source format: "); srcFormat.Print();
printf(" Destination File format: "); dstFormat.Print();
// if encoding to AAC set the bitrate
// kAudioConverterEncodeBitRate is a UInt32 value containing the number of bits per second to aim for when encoding data
// when you explicitly set the bit rate and the sample rate, this tells the encoder to stick with both bit rate and sample rate
// but there are combinations (also depending on the number of channels) which will not be allowed
// if you do not explicitly set a bit rate the encoder will pick the correct value for you depending on samplerate and number of channels
// bit rate also scales with the number of channels, therefore one bit rate per sample rate can be used for mono cases
// and if you have stereo or more, you can multiply that number by the number of channels.
if (dstFormat.mFormatID == kAudioFormatMPEG4AAC) {
UInt32 outputBitRate = 64000; // 64kbs
UInt32 propSize = sizeof(outputBitRate);
if (dstFormat.mSampleRate >= 44100) {
outputBitRate = 192000; // 192kbs
} else if (dstFormat.mSampleRate < 22000) {
outputBitRate = 32000; // 32kbs
}
// set the bit rate depending on the samplerate chosen
XThrowIfError(AudioConverterSetProperty(converter, kAudioConverterEncodeBitRate, propSize, &outputBitRate),
"AudioConverterSetProperty kAudioConverterEncodeBitRate failed!");
// get it back and print it out
AudioConverterGetProperty(converter, kAudioConverterEncodeBitRate, &propSize, &outputBitRate);
printf ("AAC Encode Bitrate: %ld\n", outputBitRate);
}
// can the Audio Converter resume conversion after an interruption?
// this property may be queried at any time after construction of the Audio Converter after setting its output format
// there's no clear reason to prefer construction time, interruption time, or potential resumption time but we prefer
// construction time since it means less code to execute during or after interruption time
UInt32 canResume = 0;
size = sizeof(canResume);
error = AudioConverterGetProperty(converter, kAudioConverterPropertyCanResumeFromInterruption, &size, &canResume);
if (noErr == error) {
// we recieved a valid return value from the GetProperty call
// if the property's value is 1, then the codec CAN resume work following an interruption
// if the property's value is 0, then interruptions destroy the codec's state and we're done
if (0 == canResume) canResumeFromInterruption = false;
printf("Audio Converter %s continue after interruption!\n", (canResumeFromInterruption == 0 ? "CANNOT" : "CAN"));
} else {
// if the property is unimplemented (kAudioConverterErr_PropertyNotSupported, or paramErr returned in the case of PCM),
// then the codec being used is not a hardware codec so we're not concerned about codec state
// we are always going to be able to resume conversion after an interruption
if (kAudioConverterErr_PropertyNotSupported == error) {
printf("kAudioConverterPropertyCanResumeFromInterruption property not supported - see comments in source for more info.\n");
} else {
printf("AudioConverterGetProperty kAudioConverterPropertyCanResumeFromInterruption result %ld, paramErr is OK if PCM\n", error);
}
error = noErr;
}
// create the destination file
XThrowIfError(AudioFileCreateWithURL(destinationURL, kAudioFileCAFType, &dstFormat, kAudioFileFlags_EraseFile, &destinationFileID), "AudioFileCreateWithURL failed!");
// set up source buffers and data proc info struct
afio.srcFileID = sourceFileID;
afio.srcBufferSize = 32768;
afio.srcBuffer = new char [afio.srcBufferSize];
afio.srcFilePos = 0;
afio.srcFormat = srcFormat;
if (srcFormat.mBytesPerPacket == 0) {
// if the source format is VBR, we need to get the maximum packet size
// use kAudioFilePropertyPacketSizeUpperBound which returns the theoretical maximum packet size
// in the file (without actually scanning the whole file to find the largest packet,
// as may happen with kAudioFilePropertyMaximumPacketSize)
size = sizeof(afio.srcSizePerPacket);
XThrowIfError(AudioFileGetProperty(sourceFileID, kAudioFilePropertyPacketSizeUpperBound, &size, &afio.srcSizePerPacket), "AudioFileGetProperty kAudioFilePropertyPacketSizeUpperBound failed!");
// how many packets can we read for our buffer size?
afio.numPacketsPerRead = afio.srcBufferSize / afio.srcSizePerPacket;
// allocate memory for the PacketDescription structures describing the layout of each packet
afio.packetDescriptions = new AudioStreamPacketDescription [afio.numPacketsPerRead];
} else {
// CBR source format
afio.srcSizePerPacket = srcFormat.mBytesPerPacket;
afio.numPacketsPerRead = afio.srcBufferSize / afio.srcSizePerPacket;
afio.packetDescriptions = NULL;
}
// set up output buffers
UInt32 outputSizePerPacket = dstFormat.mBytesPerPacket; // this will be non-zero if the format is CBR
UInt32 theOutputBufSize = 32768;
outputBuffer = new char[theOutputBufSize];
if (outputSizePerPacket == 0) {
// if the destination format is VBR, we need to get max size per packet from the converter
size = sizeof(outputSizePerPacket);
XThrowIfError(AudioConverterGetProperty(converter, kAudioConverterPropertyMaximumOutputPacketSize, &size, &outputSizePerPacket), "AudioConverterGetProperty kAudioConverterPropertyMaximumOutputPacketSize failed!");
// allocate memory for the PacketDescription structures describing the layout of each packet
outputPacketDescriptions = new AudioStreamPacketDescription [theOutputBufSize / outputSizePerPacket];
}
UInt32 numOutputPackets = theOutputBufSize / outputSizePerPacket;
// if the destination format has a cookie, get it and set it on the output file
WriteCookie(converter, destinationFileID);
// write destination channel layout
if (srcFormat.mChannelsPerFrame > 2) {
WriteDestinationChannelLayout(converter, sourceFileID, destinationFileID);
}
UInt64 totalOutputFrames = 0; // used for debgging printf
SInt64 outputFilePos = 0;
// loop to convert data
printf("Converting...\n");
while (1) {
// set up output buffer list
AudioBufferList fillBufList;
fillBufList.mNumberBuffers = 1;
fillBufList.mBuffers[0].mNumberChannels = dstFormat.mChannelsPerFrame;
fillBufList.mBuffers[0].mDataByteSize = theOutputBufSize;
fillBufList.mBuffers[0].mData = outputBuffer;
// this will block if we're interrupted
Boolean wasInterrupted = ThreadStatePausedCheck();
if ((error || wasInterrupted) && (false == canResumeFromInterruption)) {
// this is our interruption termination condition
// an interruption has occured but the Audio Converter cannot continue
error = kMyAudioConverterErr_CannotResumeFromInterruptionError;
break;
}
// convert data
UInt32 ioOutputDataPackets = numOutputPackets;
printf("AudioConverterFillComplexBuffer...\n");
error = AudioConverterFillComplexBuffer(converter, EncoderDataProc, &afio, &ioOutputDataPackets, &fillBufList, outputPacketDescriptions);
// if interrupted in the process of the conversion call, we must handle the error appropriately
if (error) {
if (kAudioConverterErr_HardwareInUse == error) {
printf("Audio Converter returned kAudioConverterErr_HardwareInUse!\n");
} else {
XThrowIfError(error, "AudioConverterFillComplexBuffer error!");
}
} else {
if (ioOutputDataPackets == 0) {
// this is the EOF conditon
error = noErr;
break;
}
}
if (noErr == error) {
// write to output file
UInt32 inNumBytes = fillBufList.mBuffers[0].mDataByteSize;
XThrowIfError(AudioFileWritePackets(destinationFileID, false, inNumBytes, outputPacketDescriptions, outputFilePos, &ioOutputDataPackets, outputBuffer), "AudioFileWritePackets failed!");
printf("Convert Output: Write %lu packets at position %lld, size: %ld\n", ioOutputDataPackets, outputFilePos, inNumBytes);
// advance output file packet position
outputFilePos += ioOutputDataPackets;
if (dstFormat.mFramesPerPacket) {
// the format has constant frames per packet
totalOutputFrames += (ioOutputDataPackets * dstFormat.mFramesPerPacket);
} else if (outputPacketDescriptions != NULL) {
// variable frames per packet require doing this for each packet (adding up the number of sample frames of data in each packet)
for (UInt32 i = 0; i < ioOutputDataPackets; ++i)
totalOutputFrames += outputPacketDescriptions[i].mVariableFramesInPacket;
}
}
} // while
if (noErr == error) {
// write out any of the leading and trailing frames for compressed formats only
if (dstFormat.mBitsPerChannel == 0) {
// our output frame count should jive with
printf("Total number of output frames counted: %lld\n", totalOutputFrames);
WritePacketTableInfo(converter, destinationFileID);
}
// write the cookie again - sometimes codecs will update cookies at the end of a conversion
WriteCookie(converter, destinationFileID);
}
}
catch (CAXException e) {
char buf[256];
fprintf(stderr, "Error: %s (%s)\n", e.mOperation, e.FormatError(buf));
error = e.mError;
}
// cleanup
if (converter) AudioConverterDispose(converter);
if (destinationFileID) AudioFileClose(destinationFileID);
if (sourceFileID) AudioFileClose(sourceFileID);
if (afio.srcBuffer) delete [] afio.srcBuffer;
if (afio.packetDescriptions) delete [] afio.packetDescriptions;
if (outputBuffer) delete [] outputBuffer;
if (outputPacketDescriptions) delete [] outputPacketDescriptions;
// transition thread state to kStateDone before continuing
ThreadStateSetDone();
return error;
}